/*
 * @Author: yuluo
 * @Date: 2025-08-28
 * @LastEditors: yuluo
 * @LastEditTime: 2025-09-06
 * @FilePath: \Projecte:\gitee\GD32F427\gd32-f427-BootLoader\BSP\src\drv_chip_flash.c
 * @Description: 
 * GD32F427ZGT6芯片FLASH驱动 此芯片flash大小1M 有两块bank
 * DBS = 1时
 * bank0 512K 0x0800_0000~0x0807_FFFF 8个扇区 0-3大小为16K 4大小为64K 5-7大小为128K
 * bank1 512K 0x0810_0000~0x0817_FFFF 8个扇区 12-15大小为16K 16大小为64K 17-19大小为128K
 * DBS = 0时
 * bank0 1M 0x0800_0000~0x080F_FFFF 16个扇区 0-3大小为16K 4大小为64K 5-11大小为128K
 * DBS默认为0
 * 支持32位整字节编程、16位半字节编程、8位字节编程
 * 支持页（4K）擦除、扇区擦除、全片擦除
 *
 * 备注：计算程序占用空间；打开map文件，找到Memory Map of the image，查看使用加载区域的大小
 * Load Region LR_IROM1 (Base: 0x08000000, Size: 0x0000154c, Max: 0x00100000, ABSOLUTE)
 * 这里的大小即为0x0000154c = 5452字节，使用的空间为0x08000000+0x0800154b
 * 如有特殊注意事项请填写
 * Copyright (c) by ${yuluo}, All Rights Reserved. 
 */

#include "drv_chip_flash.h"

/**
 * @description: 32位编程
 * @param {uint32_t} WriteAddr
 * @param {uint32_t} *pBuffer
 * @param {uint16_t} NumToWrite
 * @return {*}
 */
uint8_t gd32_WriteFlash_Word(uint32_t WriteAddr, uint32_t *pBuffer, uint16_t NumToWrite)
{
    fmc_state_enum fmc_state;
    uint32_t addr = WriteAddr;
    uint16_t i = 0;

    if (addr & 0x03) /* 地址不是4字节对齐 */
    {
        return 1; // 地址错误
    }

    /* 检查地址合法性 */
    if ((addr < (FLASH_BASE + FLASH_APP_ADDR_OFFSET)) || (addr >= FLASH_END_ADDR))
    {
        return 2; // 地址错误
    }

    /* 解锁 */
    fmc_unlock();

    /* 检查flash是否就绪 */
    fmc_state = fmc_ready_wait(FMC_TIMEOUT_COUNT);
    if (fmc_state != FMC_READY)
    {
        /* 上锁 */
        fmc_lock();
        return 3; // flash忙
    }

    for (i = 0; i < NumToWrite; i++)
    {
        fmc_state = fmc_word_program(addr, *(uint32_t *)(pBuffer + i)); // 写入数据
        if (fmc_state != FMC_READY)
        {
            /* 上锁 */
            fmc_lock();
            return 4; // 写入失败
        }
        addr += 4; // 地址递增4
    }

    /* 等待写入完成 */
    fmc_state = fmc_ready_wait(FMC_TIMEOUT_COUNT);
    if (fmc_state != FMC_READY)
    {
        /* 上锁 */
        fmc_lock();
        return 5; // 写入失败
    }
    /* 上锁 */
    fmc_lock();

    return 0; // 成功
}

/**
 * @description: 擦除扇区
 * @param {uint32_t} Sector_Address 扇区号，不要传地址
 * @return {*}
 */
uint8_t gd32_EraseFlash_Sector(uint32_t Sector_Address)
{
    fmc_state_enum fmc_state;
    uint32_t addr = Sector_Address;

    /* 这里应该判断的是是否位扇区首地址 */
    if (addr & 0x03) /* 地址不是4字节对齐 */
    {
        return 1; // 地址错误
    }

    /* 检查地址合法性 */
    // if ((addr < (FLASH_BASE + FLASH_APP_ADDR_OFFSET)) || (addr >= FLASH_END_ADDR))
    // {
    //     return 2; // 地址错误
    // }

    /* 解锁 */
    fmc_unlock();
    fmc_flag_clear(FMC_FLAG_PGSERR);

    /* 检查flash是否就绪 */
    fmc_state = fmc_ready_wait(FMC_TIMEOUT_COUNT);
    if (fmc_state != FMC_READY)
    {
        /* 上锁 */
        fmc_lock();
        return 3; // flash忙
    }

    /* 擦除扇区 */
    fmc_state = fmc_sector_erase(addr);

    /* 等待擦除完成 */
    fmc_state = fmc_ready_wait(FMC_TIMEOUT_COUNT);
    if (fmc_state != FMC_READY)
    {
        /* 上锁 */
        fmc_lock();
        return 4; // 擦除失败
    }
    /* 上锁 */
    fmc_lock();

    return 0; // 成功
}

/**
 * @description: 擦除flash指定页
 * @param {uint32_t} Page_Address
 * @return {*}
 */
uint8_t gd32_EraseFlash_Page(uint32_t Page_Address)
{
    fmc_state_enum fmc_state;
    uint32_t addr = Page_Address;

    if (addr & 0x03) /* 地址不是4字节对齐 */
    {
        return 1; // 地址错误
    }

    /* 检查地址合法性 */
    if ((addr < (FLASH_BASE + FLASH_APP_ADDR_OFFSET)) || (addr >= FLASH_END_ADDR))
    {
        return 2; // 地址错误
    }

    /* 解锁 */
    fmc_unlock();
    fmc_flag_clear(FMC_FLAG_PGSERR);

    /* 检查flash是否就绪 */
    fmc_state = fmc_ready_wait(FMC_TIMEOUT_COUNT);
    if (fmc_state != FMC_READY)
    {
        /* 上锁 */
        fmc_lock();
        return 3; // flash忙
    }

    /* 擦除页 */
    fmc_state = fmc_page_erase(addr);

    /* 等待擦除完成 */
    fmc_state = fmc_ready_wait(FMC_TIMEOUT_COUNT);
    if (fmc_state != FMC_READY)
    {
        /* 上锁 */
        fmc_lock();
        return 4; // 擦除失败
    }
    /* 上锁 */
    fmc_lock();

    return 0; // 成功
}

/**
 * @description: 返回扇区地址
 * @param {uint32_t} addr
 * @return {*}
 */
uint32_t gd32_get_sector_addr(uint32_t addr)
{
    if (addr < 0x08004000) return CTL_SECTOR_NUMBER_0;  // 16KB
    else if (addr < 0x08008000) return CTL_SECTOR_NUMBER_1;
    else if (addr < 0x0800C000) return CTL_SECTOR_NUMBER_2;
    else if (addr < 0x08010000) return CTL_SECTOR_NUMBER_3;
    else if (addr < 0x08020000) return CTL_SECTOR_NUMBER_4;  // 64KB
    else if (addr < 0x08040000) return CTL_SECTOR_NUMBER_5;  // 128KB
    else if (addr < 0x08060000) return CTL_SECTOR_NUMBER_6;
    else if (addr < 0x08080000) return CTL_SECTOR_NUMBER_7;
    else if (addr < 0x080A0000) return CTL_SECTOR_NUMBER_8;
    else if (addr < 0x080C0000) return CTL_SECTOR_NUMBER_9;
    else if (addr < 0x080E0000) return CTL_SECTOR_NUMBER_10;
    else if (addr < 0x08100000) return CTL_SECTOR_NUMBER_11;
    // 如果超过 1MB，可以继续写 bank2 的 sector 12~23
    else return CTL_SECTOR_NUMBER_11;  // 默认返回最后一个
}

/**
 * @description: 
 * @return {*}
 */
void gd32_erase_app(void)
{
    uint32_t addr = FLASH_BASE_ADDR + FLASH_APP_ADDR_OFFSET;
    uint32_t total = FLASH_END_ADDR - addr;   // APP 总空间大小
    uint32_t addr_sector;
    uint8_t res, eraser_progress = 0;

    lcd_show_string(30, 250, ((sizeof("ERASE PROGRESS") - 1) * 8), 16, 16, "ERASE PROGRESS", BLUE);
    lcd_show_string(30 + 32 + (sizeof("ERASE PROGRESS") * 8), 250, (sizeof("%") * 8), 16, 16, "%", BLUE);

    while (addr < FLASH_END_ADDR)
    {
        addr_sector = gd32_get_sector_addr(addr);
        res = gd32_EraseFlash_Sector(addr_sector);
        if(res != 0)
        {
            lcd_show_string(30, 250, 160, 16, 16, "ERASE APP FAIL!", RED);
            break; // 擦除失败
        }

        // 跳到下一个扇区起始地址
        switch (addr_sector) {
            case CTL_SECTOR_NUMBER_0: addr = 0x08004000; break;
            case CTL_SECTOR_NUMBER_1: addr = 0x08008000; break;
            case CTL_SECTOR_NUMBER_2: addr = 0x0800C000; break;
            case CTL_SECTOR_NUMBER_3: addr = 0x08010000; break;
            case CTL_SECTOR_NUMBER_4: addr = 0x08020000; break;
            case CTL_SECTOR_NUMBER_5: addr = 0x08040000; break;
            case CTL_SECTOR_NUMBER_6: addr = 0x08060000; break;
            case CTL_SECTOR_NUMBER_7: addr = 0x08080000; break;
            case CTL_SECTOR_NUMBER_8: addr = 0x080A0000; break;
            case CTL_SECTOR_NUMBER_9: addr = 0x080C0000; break;
            case CTL_SECTOR_NUMBER_10: addr = 0x080E0000; break;
            case CTL_SECTOR_NUMBER_11: addr = 0x08100000; break;
            default: addr = FLASH_END_ADDR; break;
        }
        eraser_progress = (addr - (FLASH_BASE_ADDR + FLASH_APP_ADDR_OFFSET)) * 100 / total;
        lcd_show_num(30 + (sizeof("ERASE PROGRESS") * 8), 250, eraser_progress, 3, 16, BLUE); /* 显示倒计时 */
    }
}
